#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>

#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/device.h>


#include "fs4412_led.h"

MODULE_LICENSE("Dual BSD/GPL");

//设计一个类型，描述一个设备的信息
struct led_desc{
	unsigned int dev_major;//设备号
	struct class *cls;
	struct device *dev;//创建设备文件
	char *name;
};
struct led_desc *led_dev;  //声明一个设备对象，全局共享

#define FS4412_GPF3CON	0x114001E0
#define FS4412_GPF3DAT	0x114001E4

#define FS4412_GPX1CON	0x11000C20
#define FS4412_GPX1DAT	0x11000C24

#define FS4412_GPX2CON	0x11000C40
#define FS4412_GPX2DAT	0x11000C44

static unsigned int *gpf3con;
static unsigned int *gpf3dat;

static unsigned int *gpx1con;
static unsigned int *gpx1dat;

static unsigned int *gpx2con;
static unsigned int *gpx2dat;

void fs4412_led_on(int nr)
{
	switch(nr) {
		case 1: 
			writel(readl(gpx2dat) | 1 << 7, gpx2dat);
			break;
		case 2: 
			writel(readl(gpx1dat) | 1 << 0, gpx1dat);
			break;
		case 3: 
			writel(readl(gpf3dat) | 1 << 4, gpf3dat);
			break;
		case 4: 
			writel(readl(gpf3dat) | 1 << 5, gpf3dat);
			break;
	}
}

void fs4412_led_off(int nr)
{
	switch(nr) {
		case 1: 
			writel(readl(gpx2dat) & ~(1 << 7), gpx2dat);
			break;
		case 2: 
			writel(readl(gpx1dat) & ~(1 << 0), gpx1dat);
			break;
		case 3: 
			writel(readl(gpf3dat) & ~(1 << 4), gpf3dat);
			break;
		case 4: 
			writel(readl(gpf3dat) & ~(1 << 5), gpf3dat);
			break;
	}
}

static int s5pv210_led_open(struct inode *inode, struct file *file)
{
	return 0;
}
	
static int s5pv210_led_release(struct inode *inode, struct file *file)
{
	return 0;
}
	
static long s5pv210_led_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	int nr;
	if(copy_from_user((void *)&nr, (void *)arg, sizeof(nr)))
		return -EFAULT;
	if (nr < 1 || nr > 4)
		return -EINVAL;
	switch (cmd) {
		case LED_ON:
			fs4412_led_on(nr);
			break;
		case LED_OFF:
			fs4412_led_off(nr);
			break;
		default:
			printk("Invalid argument");
			return -EINVAL;
	}
	return 0;
}

int fs4412_led_ioremap(void)
{
	int ret;
	gpf3con = ioremap(FS4412_GPF3CON, 4);
	if (gpf3con == NULL) {
		printk("ioremap gpf3con\n");
		ret = -ENOMEM;
		return ret;
	}
	gpf3dat = ioremap(FS4412_GPF3DAT, 4);
	if (gpf3dat == NULL) {
		printk("ioremap gpx2dat\n");
		ret = -ENOMEM;
		return ret;
	}
	gpx1con = ioremap(FS4412_GPX1CON, 4);
	if (gpx1con == NULL) {
		printk("ioremap gpx2con\n");
		ret = -ENOMEM;
		return ret;
	}
	gpx1dat = ioremap(FS4412_GPX1DAT, 4);
	if (gpx1dat == NULL) {
		printk("ioremap gpx2dat\n");
		ret = -ENOMEM;
		return ret;
	}
	gpx2con = ioremap(FS4412_GPX2CON, 4);
	if (gpx2con == NULL) {
		printk("ioremap gpx2con\n");
		ret = -ENOMEM;
		return ret;
	}
	gpx2dat = ioremap(FS4412_GPX2DAT, 4);
	if (gpx2dat == NULL) {
		printk("ioremap gpx2dat\n");
		ret = -ENOMEM;
		return ret;
	}
	return 0;
}

void fs4412_led_iounmap(void)
{
	iounmap(gpf3con);
	iounmap(gpf3dat);
	iounmap(gpx1con);
	iounmap(gpx1dat);
	iounmap(gpx2con);
	iounmap(gpx2dat);
}

void fs4412_led_io_init(void)
{

	writel((readl(gpf3con) & ~(0xff << 16)) | (0x11 << 16), gpf3con);
	writel(readl(gpf3dat) & ~(0x3<<4), gpf3dat);

	writel((readl(gpx1con) & ~(0xf << 0)) | (0x1 << 0), gpx1con);
	writel(readl(gpx1dat) & ~(0x1<<0), gpx1dat);

	writel((readl(gpx2con) & ~(0xf << 28)) | (0x1 << 28), gpx2con);
	writel(readl(gpx2dat) & ~(0x1<<7), gpx2dat);
}

struct file_operations my_fops = {
	.owner = THIS_MODULE,
	.open = s5pv210_led_open,
	.release = s5pv210_led_release,
	.unlocked_ioctl = s5pv210_led_unlocked_ioctl,
};

static int s5pv210_led_init(void)
{ 
	int ret;
	//0.实例化全局的设备对象 --分配空间
	led_dev = kmalloc(sizeof(struct led_desc),GFP_KERNEL);
	if(led_dev==NULL)
	{
		printk(KERN_ERR "kmalloc error \n");
		return -ENOMEM;
	}
	//1.一般申请设备资源
	//申请设备号
	led_dev->name="led_dev_text";

	led_dev->dev_major = register_chrdev(0, led_dev->name, &my_fops);
	if(led_dev->dev_major < 0){
		printk(KERN_ERR"register_chrdev error !\n");
		ret = -ENODEV;
		goto err_0;
	}
	//2.自动创建一个设备文件
	led_dev->cls = class_create(THIS_MODULE,led_dev->name);
	if(IS_ERR(led_dev->cls))
	{
		printk(KERN_ERR"class_create error !\n");
		ret = PTR_ERR(led_dev->cls);//将指针出错的具体原因转化成一个出错码
		goto err_1;
	}
	led_dev->dev = device_create(led_dev->cls,NULL,MKDEV(led_dev->dev_major,0),NULL,"led%d",1);
	if(IS_ERR(led_dev->dev))
	{
		printk(KERN_ERR"device_create error !\n");
		ret = PTR_ERR(led_dev->dev);//将指针出错的具体原因转化成一个出错码
		goto err_2;
	}
	ret = fs4412_led_ioremap();
	if (ret < 0)
		goto err_3;

	fs4412_led_io_init();
	printk("Led init\n");
	return 0;
err_3:
	device_destroy(led_dev->cls,MKDEV(led_dev->dev_major,0));
err_2:
	class_destroy(led_dev->cls);
err_1:
	unregister_chrdev(led_dev->dev_major, led_dev->name);
err_0:
	kfree(led_dev);
	return ret;
}

static void s5pv210_led_exit(void)
{
	//一般释放设备资源
	//销毁创建了的设备节点
	device_destroy(led_dev->cls,MKDEV(led_dev->dev_major,0));
	class_destroy(led_dev->cls);	
	//取消申请了的设备号
	unregister_chrdev(led_dev->dev_major,led_dev->name);
	
	fs4412_led_iounmap();
	
	printk("Led exit\n");
}

module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);
